using System;
using System.Collections.Generic;
using System.Text;
using NBusiness.CodeDom;
using System.CodeDom;
using System.IO;
using Microsoft.CSharp;
using System.CodeDom.Compiler;
using NBusiness.Properties;
using NBusiness.Data;
using NBusiness.Templates.Contracts;
using System.Reflection;
using NBusiness.CodeDom.Compiler;
using NBusiness.ESharp.Compiler;
using Microsoft.VisualBasic;
using System.Data.Common;
using NBusiness.Utilities;
using NBusiness.Query;

namespace NBusiness.Templates
{
    [ResourceDescription("CachedCollectionTemplate")]
    public class CachedCollectionTemplate : TemplateBase
    {
        #region ITemplate Members

        public override EntityResourceFile[] Generate(PartialLanguage partialLanguage, params Entity[] entities)
        {
            List<EntityResourceFile> files = new List<EntityResourceFile>();
            foreach (Entity entity in entities)
            {
                CodeCompileUnit unit = new CodeCompileUnit();
                CodeNamespace n = new CodeNamespace((entity.Parent as IFamilyUnit).Fullname);
                string fileName = "CachedCollectionBase\\" + entity.Fullname + "Collection." + partialLanguage.ToString().ToLower();

                n.Comments.Add(new CodeCommentStatement(fileName + " - '" + this.GetType().FullName + "'"));
                n.Comments.Add(new CodeCommentStatement("This file was automatically generated by NBusiness"));
                n.Comments.Add(new CodeCommentStatement("on " + DateTime.Now.ToShortDateString() + " at " + DateTime.Now.ToLongTimeString()));

                n.Imports.Add(new CodeNamespaceImport("System"));
                n.Imports.Add(new CodeNamespaceImport("System.Collections.Generic"));
                n.Imports.Add(new CodeNamespaceImport("System.Data.Common"));
                n.Imports.Add(new CodeNamespaceImport("NBusiness.Data"));
                unit.Namespaces.Add(n);

                StringWriter sw = new StringWriter();
                CodeTypeDeclaration d = CreateMainClass(entity);
                if (d != null)
                {
                    CreateConstructors(entity, d);
                    CreateFactoryMethods(entity, d);
                    CreateDataAccess(entity, d);
                    CreateLoad(entity, d);
                    CreateToInfoArray(entity, d);
                    n.Types.Add(d);

                    CodeDomProvider provider = null;
                    CodeGeneratorOptions op = new CodeGeneratorOptions();
                    switch (partialLanguage)
                    {
                        case PartialLanguage.CS:
                            provider = new CSharpCodeProvider();
                            op.BracingStyle = "C";
                            op.ElseOnClosing = false;
                            break;
                        case PartialLanguage.VB:
                            provider = new VBCodeProvider();
                            break;
                    }
                    op.BlankLinesBetweenMembers = false;
                    op.IndentString = "\t";
                    op.VerbatimOrder = true;
                    provider.GenerateCodeFromCompileUnit(unit, sw, op);
                }

                string code = sw.GetStringBuilder().ToString();
                byte[] data = Encoding.UTF8.GetBytes(code);

                EntityResourceFile file = new EntityResourceFile(
                    fileName,
                    EntityBuildAction.Compile);
                file.Data = data;

                files.Add(file);
            }
            return files.ToArray();
        }
        #endregion 

        #region Create Main Class
        protected virtual CodeTypeDeclaration CreateMainClass(Entity entity)
        {
            CodeTypeDeclaration d = null;

            if (Dependency.GetContract<IEntityBase>(entity) != null)
            {
                d = new CodeTypeDeclaration("Cached" + entity.Name + "Collection");
                d.BaseTypes.Add(new CodeTypeReference(
                    "CachedEntityBaseCollection",
                    new CodeTypeReference(entity.Name)));

                d.IsClass = true;
                d.IsPartial = true;
                d.Attributes = MemberAttributes.Public;
                d.CustomAttributes.Add(new CodeAttributeDeclaration(new CodeTypeReference(typeof(SerializableAttribute))));
            }
            else
            {
                EntityElement declaration = entity.Elements.Find(new Predicate<EntityElement>(delegate(EntityElement el)
               {
                   return el.Type == EntityElementType.EntityDeclaration;
               }));

                OnWarning(new ESharpBuildWarning(
                    ESharpErrorCode.ES0007,
                    declaration));
            }

            return d;
        }
        #endregion

        #region Create Constructors
        protected virtual void CreateConstructors(Entity entity, CodeTypeDeclaration declaration)
        {
            CreateDefaultConstructor(entity, declaration);
        }
        protected virtual void CreateDefaultConstructor(Entity entity, CodeTypeDeclaration declaration)
        {
            CodeConstructor c = new CodeConstructor();
            c.Name = entity.ToString();
            c.Attributes = MemberAttributes.Private;
            c.Parameters.Add(new CodeParameterDeclarationExpression(typeof(CriteriaBase), "c"));
            c.BaseConstructorArgs.Add(new CodeVariableReferenceExpression("c"));

            declaration.Members.Add(c);
        }
        #endregion

        #region Create Factory Methods
        protected virtual void CreateFactoryMethods(Entity entity, CodeTypeDeclaration declaration)
        {
            CreateFetchAll(entity, declaration);
        }

        #region Create FetchAll
        protected virtual void CreateFetchAll(Entity entity, CodeTypeDeclaration declaration)
        {
            CodeMemberMethod m = new CodeMemberMethod();
            m.Name = "FetchAll";
            m.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            m.ReturnType = new CodeTypeReference("Cached" + entity.Name + "Collection");
            m.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, m.Name));
            m.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));
            declaration.Members.Add(m);

            CodeObjectCreateExpression fetchAllCriteria = new CodeObjectCreateExpression("FetchAllCriteria");
            CodeObjectCreateExpression newCollection = new CodeObjectCreateExpression("Cached" + entity.Name + "Collection", fetchAllCriteria);
            CodeVariableDeclarationStatement Collection = new CodeVariableDeclarationStatement(
                "Cached" + entity.Name + "Collection",
                "items",
                newCollection);
            m.Statements.Add(Collection);

            CodeMethodReturnStatement ret = new CodeMethodReturnStatement(
                new CodeVariableReferenceExpression("items"));
            m.Statements.Add(ret);

            CodeMemberMethod m2 = new CodeMemberMethod();
            m2.Name = "FetchAll";
            m2.Attributes = MemberAttributes.Public | MemberAttributes.Static;
            m2.ReturnType = new CodeTypeReference("Cached" + entity.Name + "Collection");
            m2.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, m.Name));
            m2.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));
            declaration.Members.Add(m2);

            CodeVariableDeclarationStatement totalEntities = new CodeVariableDeclarationStatement(typeof(int), "totalEntities");
            fetchAllCriteria = new CodeObjectCreateExpression("FetchAllCriteria");
            CodeVariableDeclarationStatement criteria = new CodeVariableDeclarationStatement(
                "FetchAllCriteria", "criteria", fetchAllCriteria);
            m2.Statements.Add(criteria);
            newCollection = new CodeObjectCreateExpression("Cached" + entity.Name + "Collection", new CodeVariableReferenceExpression("criteria"));
            Collection = new CodeVariableDeclarationStatement(
                "Cached" + entity.Name + "Collection",
                "items",
                newCollection);
            CodeAssignStatement assignTotal = new CodeAssignStatement(
                new CodeVariableReferenceExpression("totalEntities"),
                new CodePropertyReferenceExpression(new CodeVariableReferenceExpression("criteria"), "TotalEntities"));
            m2.Statements.Add(Collection);
            m2.Statements.Add(assignTotal);

            CodeVariableReferenceExpression startIndex = new CodeVariableReferenceExpression("startIndex");
            CodeVariableReferenceExpression maxRows = new CodeVariableReferenceExpression("maxRows");
            CodeVariableReferenceExpression sortField = new CodeVariableReferenceExpression("sortField");
            CodeVariableReferenceExpression sortOrder = new CodeVariableReferenceExpression("sortOrder");

            m2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "startIndex"));
            m2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(int), "maxRows"));
            m2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(string), "sortField"));
            m2.Parameters.Add(new CodeParameterDeclarationExpression(typeof(SortOrder), "sortOrder"));
            CodeParameterDeclarationExpression totalOut = new CodeParameterDeclarationExpression(typeof(int), "totalEntities");
            totalOut.Direction = FieldDirection.Out;
            m2.Parameters.Add(totalOut);

            ret = new CodeMethodReturnStatement(new CodeVariableReferenceExpression("items"));
            m2.Statements.Add(ret);

            fetchAllCriteria.Parameters.Add(startIndex);
            fetchAllCriteria.Parameters.Add(maxRows);
            fetchAllCriteria.Parameters.Add(sortField);
            fetchAllCriteria.Parameters.Add(sortOrder);
        }
        #endregion
        #endregion

        #region Create DataAccess
        protected virtual void CreateDataAccess(Entity entity, CodeTypeDeclaration declaration)
        {
            CreateFetchCachedCollection(entity, declaration);
        }

        protected virtual void CreateFetchCachedCollection(Entity entity, CodeTypeDeclaration declaration)
        {
            CodeMemberMethod m = new CodeMemberMethod();
            m.Attributes = MemberAttributes.Family | MemberAttributes.Override;
            m.Name = "FetchCachedCollection";
            m.Parameters.Add(new CodeParameterDeclarationExpression(typeof(DbConnection), "cn"));
            m.Parameters.Add(new CodeParameterDeclarationExpression(typeof(CriteriaBase), "c"));
            m.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, m.Name));
            m.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));
            declaration.Members.Add(m);

            CodeVariableReferenceExpression criteriaReference = new CodeVariableReferenceExpression("c");

            CodeMethodInvokeExpression isAssignableFrom = new CodeMethodInvokeExpression(
                new CodeTypeOfExpression("FetchAllCriteria"),
                "IsAssignableFrom",
                new CodeMethodInvokeExpression(
                    criteriaReference,
                    "GetType"));

            CodeConditionStatement c1 = new CodeConditionStatement(isAssignableFrom);

            CodeMethodInvokeExpression fetchAll = new CodeMethodInvokeExpression(
                GetProvider(),
                "FetchCollection",
                new CodeVariableReferenceExpression("cn"),
                new CodePrimitiveExpression(entity.Name + "CollectionFetchAll"),
                criteriaReference,
                new CodeDelegateCreateExpression(
                    new CodeTypeReference(typeof(EventHandler<LoadEventArgs>)),
                    new CodeThisReferenceExpression(),
                    "Load"),
                new CodePropertyReferenceExpression(criteriaReference, "StartIndex"),
                new CodePropertyReferenceExpression(criteriaReference, "MaxEntities"),
                new CodePropertyReferenceExpression(criteriaReference, "SortField"),
                new CodePropertyReferenceExpression(criteriaReference, "SortOrder"));

            CodeAssignStatement totalEntities = new CodeAssignStatement(
                new CodePropertyReferenceExpression(criteriaReference, "TotalEntities"),
                fetchAll);

            c1.TrueStatements.Add(totalEntities);
            m.Statements.Add(c1);
        } 
        #endregion

        #region Create Load
        protected virtual void CreateLoad(Entity entity, System.CodeDom.CodeTypeDeclaration declaration)
        {
            CodeMemberMethod m = new CodeMemberMethod();
            m.Attributes = MemberAttributes.Override | MemberAttributes.Family;
            m.Name = "LoadCachedEntityBaseCollection";
            m.Parameters.Add(new CodeParameterDeclarationExpression(typeof(object), "sender"));
            m.Parameters.Add(new CodeParameterDeclarationExpression(typeof(LoadEventArgs), "e"));
            m.StartDirectives.Add(new CodeRegionDirective(CodeRegionMode.Start, "Load"));
            m.EndDirectives.Add(new CodeRegionDirective(CodeRegionMode.End, ""));
            declaration.Members.Add(m);

            CodeVariableReferenceExpression e = new CodeVariableReferenceExpression("e");
            CodePropertyReferenceExpression reader = new CodePropertyReferenceExpression(e, "Reader");
            CodePropertyReferenceExpression criteria = new CodePropertyReferenceExpression(e, "Criteria");
            
            CodeIterationStatement i = new CodeIterationStatement(
                new CodeSnippetStatement(""),
                new CodeMethodInvokeExpression(
                    reader,
                    "Read"),
                new CodeSnippetStatement(""));

            CodeMethodInvokeExpression add = new CodeMethodInvokeExpression(
                new CodeFieldReferenceExpression(null, "InnerList"),
                "Add",
                new CodeObjectCreateExpression(
                    entity.Name,
                    reader,
                    criteria));

            i.Statements.Add(add);
            m.Statements.Add(i);
        } 
        #endregion

        #region Create ToInfoArray
        protected virtual void CreateToInfoArray(Entity entity, CodeTypeDeclaration declaration)
        {
            IInfoObject toInfoArray = Dependency.GetContract<IInfoObject>(entity);
            if (toInfoArray != null)
            {
                CodeMemberMethod m = toInfoArray.ToInfoArrayMethod(entity);
                declaration.Members.Add(m);
            }
            else
            {
                EntityElement d = entity.Elements.Find(new Predicate<EntityElement>(delegate(EntityElement el)
                  {
                      return el.Type == EntityElementType.EntityDeclaration;
                  }));

                OnWarning(new ESharpBuildWarning(
                    ESharpErrorCode.ES0008,
                    d));
            }
        }
        #endregion

        #region Helpers
        private CodeExpression GetProvider()
        {
            return new CodeCastExpression(typeof(IDatabaseProvider),
                new CodeMethodInvokeExpression(
                    new CodePropertyReferenceExpression(
                        new CodeTypeReferenceExpression(typeof(Settings)),
                        "Instance"),
                    "GetSetting",
                    new CodeFieldReferenceExpression(
                        new CodeTypeReferenceExpression(typeof(SettingsType)),
                        "Provider"),
                    new CodeMethodInvokeExpression(new CodeThisReferenceExpression(), "GetType")));
        }
        #endregion
    }
}
